﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
using Zhy.ModbusLib.Base;

namespace Zhy.ModbusLib
{
    public class ModbusTCP : ModbusSocket
    {
        #region 初始化
        public ModbusTCP() : this("127.0.0.1")
        { }
        public ModbusTCP(string IP) : this(IP, 502)
        { }
        public ModbusTCP(string IP, int port) : base(ProtocolType.Tcp)
        {
            this.IP = IP;
            this.Port = port;
        }
        #endregion

        #region Modbus TCP 读取操作
        int _tid = 0;
        /// <summary>
        /// 读取 4个存储区域
        //  从站地址、功能码、起始地址、数量
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始位置</param>
        /// <param name="count">读取个数</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        public override byte[] Read(byte slave, Functions func, ushort start, ushort count)
        {
            int tid = this.CreateTID(); // 获取TCP的-TransactionID 

            List<byte> bytes = new List<byte>()
            {
                (byte)(tid/256), // 设置TCP地址高位
                (byte)(tid%256), // 设置TCP地址低位
                0x00,0x00, // Modbus协议ID  （基本固定的）
                0x00,0x06 // 设置后续还有多少字节 - 读取线圈和寄存器基本是固定6个字节
            };
            // 添加读取的发送报文
            bytes.AddRange(this.GetReadBytes(slave, (byte)func, start, count));
            // 发送和接收报文数据
            byte[] resp = this.SendAndReceive(bytes.ToArray(), -1);

            return Check(resp);
        }

        /// <summary>
        /// 读取 4个存储区域
        //  从站地址、功能码、起始地址、数量
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始位置</param>
        /// <param name="count">读取个数</param>
        /// <param name="callback">委托用于处理返回的数据</param>
        /// <returns></returns>
        public override void ReadAsync(
             byte slave, Functions func, ushort start, ushort count,
             int handler, Action<byte[], int, Exception> callback)
        {
            int tid = this.CreateTID(); // 获取TCP的-TransactionID 

            List<byte> bytes = new List<byte>()
            {
                (byte)(tid/256), // 设置TCP地址高位
                (byte)(tid%256), // 设置TCP地址低位
                0x00,0x00, // Modbus协议ID  （基本固定的）
                0x00,0x06 // 设置后续还有多少字节 - 读取线圈和寄存器基本是固定6个字节
            };
            // 添加读取的发送报文
            bytes.AddRange(this.GetReadBytes(slave, (byte)func, start, count));

            // 向队列里添加委托任务-用于处理异步返回的数据
            this.asyncModels.Add(new AsyncModel
            {
                Handler = handler, // 请求ID
                ReqBytes = bytes.ToArray(), // 请求数据
                Completed = callback // 回调委托
            });
            // 会有一个线程从队列里取任务执行
        }
        #endregion

        #region Modbus TCP 写入操作
        /// <summary>
        /// Modbus TCP 写入操作
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始地址</param>
        /// <param name="count">字节数</param>
        /// <param name="datas">写入数据</param>
        /// <exception cref="Exception"></exception>
        public override void Write(byte slave, Functions func, ushort start, ushort count, byte[] datas)
        {
            int tid = this.CreateTID(); // 获取TCP的-TransactionID 
            List<byte> bytes = new List<byte>()
            {
                (byte)(tid/256), // 设置TCP地址高位
                (byte)(tid%256), // 设置TCP地址低位
                0x00,0x00 // Modbus协议ID  （基本固定的）
            };
            // 写入的发送报文拼接
            byte[] write = this.GetWriteBytes(slave, (byte)func, start, count, datas);
            // 添加后续要写入报文的长度
            bytes.Add((byte)(write.Length / 256)); // 后续字节数高位
            bytes.Add((byte)(write.Length % 256)); // 后续字节数低位
            bytes.AddRange(write); // 添加后续要写入的报文数据
            // 发送和接收报文数据
            byte[] resp = this.SendAndReceive(bytes.ToArray(), -1);

            Check(resp); // 返回报文校验
        }

        /// <summary>
        /// Modbus TCP 写入操作
        /// </summary>
        /// <param name="slave">从站地址</param>
        /// <param name="func">功能码</param>
        /// <param name="start">起始地址</param>
        /// <param name="count">字节数</param>
        /// <param name="datas">写入数据</param>
        /// <param name="callback">委托用于处理返回的数据</param>
        public override void WriteAsync(
            byte slave, Functions func, ushort start, ushort count, byte[] datas,
            int handler, Action<byte[], int, Exception> callback)
        {
            int tid = this.CreateTID(); // 获取TCP的-TransactionID 
            List<byte> bytes = new List<byte>()
            {
                (byte)(tid/256), // 设置TCP地址高位
                (byte)(tid%256), // 设置TCP地址低位
                0x00,0x00 // Modbus协议ID  （基本固定的）
            };
            // 写入的发送报文拼接
            byte[] write = this.GetWriteBytes(slave, (byte)func, start, count, datas);
            // 添加后续要写入报文的长度
            bytes.Add((byte)(write.Length / 256)); // 后续字节数高位
            bytes.Add((byte)(write.Length % 256)); // 后续字节数低位
            bytes.AddRange(write); // 添加后续要写入的报文数据

            // 向队列里添加委托任务-用于处理异步返回的数据
            this.asyncModels.Insert(0, new AsyncModel
            {
                Handler = handler, // 请求ID
                ReqBytes = bytes.ToArray(), // 请求数据
                Completed = callback // 回调委托
            });
            // 会有一个线程从队列里取任务执行
        }
        #endregion

        #region 设置TCP地址
        /// <summary>
        /// lock锁-防止ID重复
        /// </summary>
        private static readonly object _lockObj = new object();
        /// <summary>
        /// 设置TCP地址
        /// </summary>
        /// <returns></returns>
        private int CreateTID()
        {
            // TCP地址累加但又不能大于2个字节
            lock (_lockObj)
            {
                _tid++;
                _tid = _tid % 65525;
                return _tid;
            }
        }
        #endregion

        /// <summary>
        /// 返回的报文进行校验
        /// </summary>
        /// <param name="resp">返回的报文</param>
        /// <param name="len">正常报文接收长度</param>
        /// <returns></returns>
        /// <exception cref="Exception"></exception>
        protected override byte[] Check(byte[] resp)
        {
            // 报文异常码检查
            // 0x03          0x83
            // 0000 0011     1000 0011
            if (resp[1] > 0x80) // 功能码>0x80的是异常报文-否则是正常报文
            {
                // resp[2] 是异常码 - 获取异常码的故障原因
                throw new Exception(ModbusBase.Errors[resp[2]]);// 异常码
            }
            // 截取实际的报文数据-数据字节
            return resp.ToList().GetRange(3, resp.Length - 3).ToArray();
        } 
    }
}
